﻿using CRL.Core.Remoting;
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using CRL.Core.Extension;
using CRL.Transaction.DbStatus;

namespace CRL.Transaction
{
    public class SagaControl : ITransControl
    {
        TransOptions _option;
        List<IStep> steps = new List<IStep>();
        public bool FromLoad { get; set; }
        bool fromNetCoreInjection;
        public TransStatus Status { get; set; }

        public SagaControl()
        {
        }
#if NETSTANDARD
        IServiceProvider provider;
        public SagaControl(IServiceProvider _provider, ITransInfoManage _transInfoManage)
        {
            provider = _provider;
            fromNetCoreInjection = true;
            //transInfoManage = _transInfoManage;
        }
        public void SetServiceProvider(IServiceProvider _provider)
        {
            provider = _provider;
        }
#endif
        public TransactionType TransactionType => TransactionType.Saga;
        public string LastError { get; set; }
        public ITransControl Start(TransOptions option)
        {
            if (string.IsNullOrEmpty(option.TId))
            {
                option.TId = Guid.NewGuid().ToString();
            }
            _option = option;
            return this;
        }
        public SagaControl Then<T>(object args = null) where T : IStep
        {
            return Then(typeof(T), args, null) as SagaControl;
        }

        public ITransControl Then(Type type, object args, TransStatus? newStatus)
        {
            if (type == null)
            {

                throw new ArgumentNullException("type");
            }
            IStep sagaStep;
#if NETSTANDARD
            if (provider != null && fromNetCoreInjection)
            {
                sagaStep = provider.GetService(type) as IStep;
            }
            else
            {
                sagaStep = System.Activator.CreateInstance(type) as IStep;
            }
#else
            sagaStep = System.Activator.CreateInstance(type) as IStep;
#endif
            sagaStep.TransOptions = _option;
            steps.Add(sagaStep);
            sagaStep.Step = steps.Count;
            sagaStep.SetArgs(args);
            if (newStatus != null)
            {
                sagaStep.Status = newStatus.Value;
            }
            return this;
        }
        bool isDelegate;
        public SagaControl Then(Action commit, Action cancel)
        {
            isDelegate = true;
            var sagaStep = new CustomSagaStep(commit, cancel);
            _option.Persistent = false;
            steps.Add(sagaStep);
            sagaStep.Step = steps.Count;
            //sagaStep.SetArgs(args);
            return this;
        }
        void Log(IStep step, string msg)
        {
            if (!_option.UseLog)
            {
                return;
            }
            var logMsg = $"{_option.Name} step{step.Step}[{step.Name}] {msg}";
            Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} {logMsg}");
            Core.EventLog.Log(logMsg, GetType().Name);
        }
        /// <summary>
        /// Saga事务，根据运行状态返回结果，NeedRepair以确定是否需要手动干预
        /// </summary>
        /// <returns></returns>
        public ExecuteResult Execute()
        {
            if (_option == null)
            {
                throw new Exception("_option is null");
            }
            var infoManage = TransConfig.GetTransInfoManage();
            if (isDelegate)
            {
                infoManage = null;
            }
            //delegate不写入状态维护
            if (!string.IsNullOrEmpty(_option.CheckKey) && infoManage != null)
            {
                TransLoader loader;
#if NETSTANDARD
                if (fromNetCoreInjection)
                {
                    loader = provider.GetService(typeof(TransLoader)) as TransLoader;
                }
                else
                {
                    loader = new TransLoader();
                }
#else
                loader = new TransLoader();
#endif
                var info = infoManage.QueryChek(_option);
                if (info != null)
                {
                    var allSteps = infoManage.QuerySteps(new List<string> { info.TId });
                    return loader.Load(info, allSteps).Execute();
                }
            }
            if (_option.Persistent && infoManage != null)
            {
                var masterInfo = new DbStatus.TransMasterInfo
                {
                    TId = _option.TId,
                    Name = _option.Name,
                    ProjectName = TransConfig.projectName,
                    MaxRetryCount = _option.MaxRetryCount,
                    RetryInterval = _option.RetryInterval.TotalSeconds,
                    StatusName = TransStatus.Pending.ToString(),
                    TransactionType = TransactionType,
                    Args = _option.Args.ToJson(),
                    ArgsType = _option.Args?.GetType().AssemblyQualifiedName,
                    CheckKey = _option.CheckKey
                };
                var steps2 = steps.Select(b => new DbStatus.TransStepInfo
                {
                    Args = b.Args.ToJson(),
                    ArgsType = b.GetArgsType().AssemblyQualifiedName,
                    TId = _option.TId,
                    FromNetCoreInjection = fromNetCoreInjection,
                    TypeName = b.GetType().AssemblyQualifiedName,
                    Step = b.Step,
                    StatusName = TransStatus.Pending.ToString(),
                    TransactionType = TransactionType,
                    Name = b.Name
                }).ToList();
                bool a;
                string error="";
                try
                {
                    a = infoManage.SaveSteps(masterInfo, steps2, out error);
                }
                catch (Exception ex)
                {
                    a = false;
                    error = ex.Message;
                }
                if (!a)
                {
                    Core.EventLog.Log($"提交{TransactionType} 失败 {error} {new { master = masterInfo, setps = steps2 }.ToJson()}", GetType().Name);
                    throw new Exception($"事务持久化失败 {error}");
                }
            }
            int maxCancelStep = 0;
            if (FromLoad)
            {
                //取消失败，步骤前需要重新取消
                //提交失败，需重新提交
                var maxError = steps.FindAll(b => b.Status == TransStatus.CancelError).OrderByDescending(b => b.Step).FirstOrDefault();
                if (maxError != null)
                {
                    maxCancelStep = maxError.Step;
                }
            }
            FromLoad = true;//使可重复执行
            IStep errorStep = null;
            for (var i = 0; i < steps.Count; i++)
            {
                var current = steps[i] as AbsSagaStepBase;
                var cancelSetp = -1;
                try
                {
                    if (maxCancelStep > 0 && i == maxCancelStep - 1)
                    {
                        cancelSetp = i;
                        Log(current, "加载上次异常，开始自动取消");
                        goto label1;
                    }
                    if (current.Status == TransStatus.Pending || current.Status == TransStatus.CommitError)
                    {
                        current.Commit();
                        current.Status = TransStatus.Commited;
                        var msg = $"{current.Status} 成功";
                        if (!current.defaultCommit)
                        {
                            infoManage?.UpdateStatus(_option.TId, i, current.Status, "Commit 成功");
                        }
                        else
                        {
                            msg += " NotImplemented";
                        }
                        Log(current, msg);
                    }
                    continue;
                }
                catch (Exception e)
                {
                    cancelSetp = i - 1;
                    Log(current, $"Commit 异常，开始取消 {e}");
                    current.Status = TransStatus.CommitError;
                    infoManage?.UpdateStatus(_option.TId, i, TransStatus.CommitError, $"{e.Message} {e.InnerException?.Message}");
                    //errorStep = current;
                    goto label1;
                }
            label1:
                for (var n = cancelSetp; n >= 0; n--)
                {
                    var pre = steps[n];
                    var pollyKey = $"{_option.TId}_step_{n}";
                    var pollyData = PollyExtension.Invoke(new PollyAttribute { RetryCount = _option.MaxRetryCount, RetryInterval = _option.RetryInterval, }, () =>
                    {
                        if (pre.Status == TransStatus.Commited || pre.Status == TransStatus.CancelError)
                        {
                            pre.Cancel();
                            pre.Status = TransStatus.Canceled;
                            var msg = $"{pre.Status} 成功";
                            if (!current.defaultCancel)
                            {
                                infoManage?.UpdateStatus(_option.TId, n, pre.Status, "Cancel 成功");
                            }
                            else
                            {
                                msg += " NotImplemented";
                            }
                            Log(pre, msg);
                        }
                        return new PollyExtension.PollyData<string>() { Data = "ok" };
                    }, pollyKey);
                    if (!string.IsNullOrEmpty(pollyData.Error))
                    {
                        errorStep = pre;
                        LastError = $"Cancel 失败 {pollyData.Error}";
                        Log(pre, $"Cancel 失败 {pollyData.InnerException}");
                        pre.Status = TransStatus.CancelError;
                        Status = TransStatus.CancelError;
                        infoManage?.UpdateStatus(_option.TId, n, TransStatus.CancelError, pollyData.Error);
                        infoManage?.UpdateStatus(_option.TId, TransStatus.CancelError, pollyData.Error);
                        //throw new Exception(eroMsg);
                        return new ExecuteResult
                        {
                            AbstractResult = false,
                            LastError = LastError,
                            NeedRepair = true,
                            Status = TransStatus.CancelError,
                            ErrorStep = errorStep
                        };
                    }
                }
                Status = TransStatus.Canceled;
                infoManage?.UpdateStatus(_option.TId, TransStatus.Canceled, "Commit 失败，已取消");
                LastError = "Commit 失败，已取消";
                return new ExecuteResult
                {
                    AbstractResult = false,
                    LastError = LastError,
                    NeedRepair = false,
                    Status = TransStatus.Canceled,
                    ErrorStep = errorStep
                };
            }
            Status = TransStatus.Commited;
            infoManage?.UpdateStatus(_option.TId, TransStatus.Commited, "完成");
            return new ExecuteResult
            {
                AbstractResult = true,
                LastError = LastError,
                NeedRepair = false,
                Status = TransStatus.Commited,
                ErrorStep = errorStep
            };
        }

    }

}
